#!/usr/bin/perl -w
# Written by Steve Fox 
# any questions about this can be attempted to directed to: foxsteven@comcast.net
use warnings;  
use strict;
use File::Basename;
use File::Spec;
use XML::LibXML;
use Getopt::Long;
use Date::Calc qw(Add_Delta_DHMS); 
use File::Glob ':glob';

use vars qw($startDir
	    @searchPath
	    @jpgFiles
	    @moddFiles
	    $filterTree
	    $fixFileExt
		$owOrg
	    %labelHash
	    @imageTypes
		$debug
		$startTime
		$endTime
		$fileCount
		$failCount
		$renameCount
		$moddCount
		$allCount
);

#this fixes the modd file names to be <file>.modd instead of <file>.JPG.modd
sub fixFileExts {
     my $file = $_[0];
     my $orig = $_[0];
     foreach (@imageTypes) {
        if ( $file =~  /\.$_\./i ) {
           $file =~ s/$_\.//ig;
		   if ($debug) {
				print LOGFILE "Attempting to rename $orig to $file\n";
			}
  	       rename $orig, $file or die "Unable to rename $orig to $file";
		   $renameCount++;
		}
     }
	 return $file;
} #end fixFileExts

#this function only gets .modd files, find those first then look for jpgs to edit.
sub getModds {
  my $root_dir = $_[0];
 
  my @all_files = bsd_glob "$root_dir/*";
  foreach (@all_files) {
    print LOGFILE "Working on $_\n";
	$allCount++;
	my $pushFile = $_;
	if ($fixFileExt) {
		$pushFile = &fixFileExts($_);
	}
    if (-d $pushFile) {
		if ($debug) {
			print LOGFILE "Searching $_\n";
		}
        &getModds("$_"); 
    } elsif ($pushFile =~ /.modd$/i) {
		if ($debug) {
			print LOGFILE "Pushing $pushFile\n";
		}
        push @moddFiles, $pushFile;
    }
  }
} # end getModds

sub parseXML {
  my $xmlFile = $_[0];
  my $imageFile = $_[1];
  my @tags;
  my @transTags;
  my $tag;
  my @strings;
  my @keys;
  my $date;
  my $rating;
  my $dateFlag = "";
  my $tagFlag = "";
  my $ratingFlag = "";
  
  # create object
  my $parser = XML::LibXML->new();
  my $tree = $parser->parse_file($xmlFile); 
  
  foreach my $string ($tree->findnodes('//array/string')) { 
     push @tags, $string->to_literal;
  }
  foreach my $real ($tree->findnodes('//dict/real')) { 
     $date = $real->to_literal;
  }
  foreach my $integer ($tree->findnodes('//dict/integer')) { 
     $rating = $integer->to_literal;
  }

  #call to exiftool
  if ($date) {
 	#if date exists, seems to be number of days since 12/30/1899 and a percentage of the day in time, this breaks it to 
	#normal time with Date::Calc, sample usage below
	#my ($year,$month,$day, $hour,$min,$sec) = Add_Delta_DHMS($year,$month,$day, $hour,$min,$sec,$Dd,$Dh,$Dm,$Ds);
	my ($Dd, $remainder) = split /\./, $date;
	my $total_seconds = 60 * 60 * 24;
	my $percent = ".$remainder";
	my $current_seconds = $percent * $total_seconds;
	my ($hrs, $xMin) = split /\./, $current_seconds / 3600;
	my $pMin = ".$xMin";
	my $dMin = $pMin * 3600;
	my ($mins, $xSec) = split /\./, $dMin / 60;
	my $pSec = ".$xSec";
	my $dSec = $pSec * 60;
	my ($secs, $subSec) = split /\./, $dSec;
	my ($year,$month,$day,$hour,$min,$sec) = Add_Delta_DHMS(1899,12,30,0,0,0,$Dd,$hrs,$mins,$secs);
	# since the -DateTimeOriginal wants 2 digits in each field
	if ($month < 10) {
		$month = "0${month}";
	}
	if ($day < 10) {
		$day = "0${day}";
	}
	if ($hour < 10) {
		$hour = "0${hour}";
	}
	if ($min < 10) {
		$min = "0${min}";
	}
	if ($sec < 10) {
		$sec = "0${sec}";
	}
	$dateFlag = "-DateTimeOriginal=\"$year:$month:$day $hour:$min:$sec\"";
  }
  if ($rating) {
	$ratingFlag = "-xmp-xmp:Rating=$rating";
  }
  if (@tags) {
    foreach(@tags) {
    	push @transTags, $labelHash{$_};
    }
    my $divTags = join( '","', @transTags );
    $tagFlag = "-xmp-dc:subject=\"$divTags\"";
  }
  my $exiftoolExec = "perl C:\\exiftool\\exiftool $tagFlag $dateFlag $ratingFlag \"$imageFile\" $owOrg";
  if ($debug) {
	print LOGFILE "executing: $exiftoolExec\n";
  }
  chomp(my @sysOut = `$exiftoolExec`);
  print LOGFILE "@sysOut\n";
  my $out = "@sysOut";
  if ( $out =~ /1 image files updated/ ) {
	$fileCount++;
  } else {
	$failCount++;
  }
} # end parseXML

sub parseFilterTree {
  my @strings;                   
  my $key;
  my $value;
  # create object
  my $parser = XML::LibXML->new();
  my $tree = $parser->parse_file($filterTree); 
  
  foreach my $string ($tree->findnodes('//string')) { 
     push @strings, $string->to_literal;
  }

  my $count = 0;
  #since the XML is stored under consective strings, we pop the 1st one to be the key and 2nd to be the value, and repeat
  #this will be a bit larger than we need, but the extra data won't be used so its harmless.
  #total in array divided by 2 to figure out how much to loop
  my $endCount = $#strings/2;
  for ($count..$endCount) {
      $key = pop (@strings);
      $value = pop (@strings);
      $labelHash{$key} = $value;
  }
} # end parseFilterTree

#########
# MAIN PROGRAM
#########
our ($startDir, $filterTree, $fixFileExt, $owOrg, $debug);
if (!GetOptions ('startDir=s'	=> \$startDir,   #starting Directory to search
				 'filterTree=s' => \$filterTree, #path to filterTree.xml needed to pair up label names
            	 'fixFileExt'   => \$fixFileExt, #fix file extentions removing extra .jpg in the modd or jpg file names
            	 'owOrg'        => \$owOrg,      #overwrite files without _original names
            	 'debug'        => \$debug       #adds extra info to log file
				)) {
	print "ERROR: required options not specified.\n\n";
	exit 9;
}
$fileCount = 0;
$failCount = 0;
$renameCount = 0;
$moddCount = 0;
$allCount = 0;
$startTime = time;
open(LOGFILE, ">getTagInfo.log") or die "Unable to create file 'getTagInfo.log'\nPlease Fix...Program Exiting.'\n";

@imageTypes = qw/ jpg /;
&getModds($startDir);

if ($debug) {
  $debug = "true";
} else {
  $debug = "";
}

if ($owOrg) {
  $owOrg = "-overwrite_original";
} else {
  $owOrg = "";
}
&parseFilterTree;   
my $count = 0;
foreach (@moddFiles) {
   my @suffixlist = qw/ modd /;
   my ($name,$path,$suffix) = fileparse($_,@suffixlist);
   my $loopexit = "no";
   my $editFile = "none";
   my $jpgFile;
   until ($loopexit eq "yes") {
   	foreach (@imageTypes) {
	  chomp($jpgFile = File::Spec->catfile($path, $name.$_));
	  if (-e $jpgFile) {
	      $editFile = $jpgFile;
	      $loopexit = "yes";
 	  }
	  $loopexit = "yes";  	   
    }
   }
   if ($editFile ne "none") {
       &parseXML($moddFiles[$count],$editFile);
   } else {
      print LOGFILE "Cannot find $name.with extentions: @imageTypes inside of $path.\n $_ ignored!!!\n"
   }
   $count++;
}
$endTime = time;
my $totalTime = $endTime - $startTime;
my $runtimeHrs = $totalTime / 60 / 60;
$runtimeHrs = sprintf("%.3f", $runtimeHrs);
$moddCount = @moddFiles; 
my $moddReport = "Processed $allCount files including $moddCount modd files under $startDir";
my $renameReport = "Fixed $renameCount redundant file extentions";
my $timeReport = "Edited $fileCount image files in $totalTime seconds. ($runtimeHrs Hrs.)";
my $failReport = "There were $failCount unsuccessful updates please see getTagInfo.log";
my $finalPrint = "$moddReport\n$renameReport\n$timeReport\n$failReport\n";
print "$finalPrint";
print LOGFILE "$finalPrint";
close LOGFILE;
exit 0;
